package com.linran.webjsonrsp.advice;

import com.alibaba.fastjson.JSONObject;
import com.linran.webjsonrsp.annotation.ResponseResultBody;
import com.linran.webjsonrsp.bean.Result;
import com.linran.webjsonrsp.bean.exception.ResultException;
import com.linran.webjsonrsp.controller.JsonController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;

/**
 * 统一返回JSON格式处理&控制层异常处理
 *
 * @author lin
 * @date 2020-05-05 18:01
 */
@Slf4j
@RestControllerAdvice(assignableTypes = JsonController.class)
//除了自定义注解@ResponseResultBody，也可以通过assignableTypes进行标注拦截哪些控制层进行统一json处理
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {
    private static final Class<? extends ResponseResultBody> ANNOTATION_TYPE = ResponseResultBody.class;

    /**
     * 判断类或方法是否使用了ResponseResultBody注解
     *
     * @param returnType    方法返回参数
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
     * 当类或者方法使用了 @ResponseResultBody 就会调用这个方法
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //防止嵌套封装
        if (body instanceof Result) {
            return body;
        }
        return Result.success(body);
    }

    /**
     * 提供对标准Spring MVC异常的处理
     * <p>
     * 对于异常返回的个人总结
     * 1.进入此处异常处理后，就不会再进入到上面的全局统一json返回封装，所以下面需要对返回内容body的进行统一返回封装
     * 2.有人说spring已经定义了ResponseEntity怎么还自定义Result进行重复造轮子，因为ResponseEntity是包含header,body,attribute等所有响应信息的类，
     * 但是并没有对返回body的格式内容进行任何定义，而这里的全局统一json返回对象Result才是对body进行格式规范
     * <p>
     * 关于WebRequest
     * 基本上，除了WebRequestInterceptor之外，你不应该使用WebRequest。他们引入了这个接口，以便能够编写适用于servlet和portlet的拦截器。
     * 除此之外，如果你真的需要访问Spring MVC控制器中的请求，你应该使用标准的HttpServletRequest。
     *
     * @param ex      the target exception
     * @param request the current request
     */
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Result<?>> exceptionHandler(Exception ex, HttpServletRequest request) {
        log.error("ExceptionHandler: {}", ex.getMessage());
        HttpHeaders headers = new HttpHeaders();
        if (ex instanceof ResultException) {
            return this.handleResultException((ResultException) ex, headers, request);
        }
        // TODO: 2019/10/05 galaxy 这里可以自定义其他的异常拦截
        return this.handleException(ex, headers, request);
    }

    /**
     * 对自定义ResultException异常类返回结果的处理
     */
    protected ResponseEntity<Result<?>> handleResultException(ResultException ex, HttpHeaders headers, HttpServletRequest request) {
        Result<?> body = Result.failure(ex.getMessage(), ex.getResultStatus());
        HttpStatus status = ex.getResultStatus().getHttpStatus();
        return this.handleExceptionInternal(ex, body, headers, status, request);
    }

    /**
     * 异常类的统一处理
     */
    protected ResponseEntity<Result<?>> handleException(Exception ex, HttpHeaders headers, HttpServletRequest request) {
        Result<?> body = Result.failure();
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return this.handleExceptionInternal(ex, body, headers, status, request);
    }

    /**
     * org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler#handleExceptionInternal(java.lang.Exception, java.lang.Object, org.springframework.com.linran.javaweb.http.HttpHeaders, org.springframework.com.linran.javaweb.http.HttpStatus, org.springframework.web.context.request.WebRequest)
     * <p>
     * A single place to customize the response body of all exception types.
     * <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE}
     * request attribute and creates a {@link ResponseEntity} from the given
     * body, headers, and status.
     */
    protected ResponseEntity<Result<?>> handleExceptionInternal(
            Exception ex, Result<?> body, HttpHeaders headers, HttpStatus status, HttpServletRequest request) {
        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex);
            //原始方法是使用的WebRequest将异常信息放入到request里
            //request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        System.out.println(JSONObject.toJSONString(new ResponseEntity<>(body, headers, status)));
        return new ResponseEntity<>(body, headers, status);
    }
}
